﻿using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Moq;
using System;
using System.Linq;
using System.Reflection;

namespace SmarterCharger.Library.Tests.Unit.Infrastructure
{
    public static class ConstructorTest
    {
        public static void Verify<T>()
        {
            var constructors = typeof(T).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
            Assert.AreNotEqual(constructors.Length, 0, "No constructor found");
            Assert.IsFalse(constructors.Length > 1, "More than one constructor found");

            TestConstructor(constructors.Single());
        }
        private static void TestConstructor(ConstructorInfo constructorInfo)
        {
            var parameters = constructorInfo.GetParameters();
            foreach (var parameter in parameters)
            {
                TestConstructorWithParameterNull(constructorInfo, parameters, parameter);
            }

            TestConstructorWithAllParameters(constructorInfo, parameters);
        }
        private static void TestConstructorWithParameterNull(ConstructorInfo constructorInfo, ParameterInfo[] parameters, ParameterInfo parameter)
        {
            var parameterValues = GetParameterValues(parameters, parameter);

            try
            {
                constructorInfo.Invoke(parameterValues);
                Assert.Fail("ArgumentNullException with parameter '{0}' expected. (NoException)", parameter.Name);
            }
            catch (TargetInvocationException ex)
            {
                var argumentNullException = ex.InnerException as ArgumentNullException;
                if (argumentNullException != null)
                {
                    if (argumentNullException.ParamName != parameter.Name)
                    {
                        Assert.Fail("ArgumentNullException with parameter '{0}' expected. (Parameter: '{1}')",
                        parameter.Name, argumentNullException.ParamName);
                    }
                }
                else
                {
                    Assert.Fail("ArgumentNullException with parameter '{0}' expected. Exception: '{1}'", parameter.Name,
                    ex.ToString());
                }
            }
        }
        private static object[] GetParameterValues(ParameterInfo[] parameters, ParameterInfo parameter)
        {
            var parameterValues = new object[parameters.Length];
            for (var i = 0; i < parameterValues.Length; i++)
            {
                parameterValues[i] = CreateParameterValue(parameter, parameters[i]);
            }
            return parameterValues;
        }
        private static void TestConstructorWithAllParameters(ConstructorInfo constructorInfo, ParameterInfo[] parameters)
        {
            var parameterValues = GetParameterValues(parameters, null);
            try
            {
                constructorInfo.Invoke(parameterValues);
            }
            catch (Exception ex)
            {
                Assert.Fail("No exception expected. Exception: '{0}'", ex.ToString());
            }
        }
        private static object CreateParameterValue(ParameterInfo nullParameter, ParameterInfo parameter)
        {
            return nullParameter == null || nullParameter != parameter
            ? (Activator.CreateInstance(typeof(Mock<>).MakeGenericType(parameter.ParameterType)) as Mock).Object
            : null;
        }
    }
}
